package com.zhangzhan.gulimallseckill.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhangzhan.gulimallcommcon.tos.MemberTo;
import com.zhangzhan.gulimallcommcon.tos.SeckillCreateTo;
import com.zhangzhan.gulimallcommcon.util.MyCollUtil;
import com.zhangzhan.gulimallcommcon.util.R;
import com.zhangzhan.gulimallseckill.feign.CouponFeignService;
import com.zhangzhan.gulimallseckill.feign.ProductFeignService;
import com.zhangzhan.gulimallseckill.interceptor.SeckillInterceptor;
import com.zhangzhan.gulimallseckill.service.SeckillService;
import com.zhangzhan.gulimallseckill.tos.SeckillSessionTo;
import com.zhangzhan.gulimallseckill.tos.SeckillSkuRelationTo;
import com.zhangzhan.gulimallseckill.tos.SkuInfoTo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import static com.zhangzhan.gulimallcommcon.constant.orderAndWareUnLockCons.OrderCancelMqConstant.ORDER_EVENT_EXCHANGE;
import static com.zhangzhan.gulimallcommcon.constant.orderAndWareUnLockCons.OrderCancelMqConstant.SECKILL_CREATE_ORDER;

/**
 * @author 张一恒
 * @version 1.0
 * @description:
 * @date 2024/7/4 18:35
 */
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    //秒杀活动key前缀
    private final String SECKILL_SESSION = "seckill:session:";
    //秒杀活动商品key前缀
    private final String SECKILL_SKUS = "seckill:skus";
    private final String SECKILL_SEMAPHORE = "seckill:semaphore:";
    private final String SECKILL_KILL_USER = "seckill:kill:user:";


    @Override
    public void shelves3DayAfterSeckill() {
        log.info("执行三天后需要上架的秒杀活动");
        //三天后需要上架的秒杀活动
        R r = couponFeignService.getSeckillData();
        if (Objects.equals(r.getCode(), 0)) {
            List<SeckillSessionTo> sessionTos = JSONObject.parseArray(r.getData(), SeckillSessionTo.class);
            if (MyCollUtil.isNotEmpty(sessionTos)) {
                BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKUS);

                //存入秒杀场次
                sessionTos.forEach(po -> {
                    String key = SECKILL_SESSION + po.getStartTime().getTime() + "_" + po.getEndTime().getTime();
                    Boolean hassed = stringRedisTemplate.hasKey(key);
                    //如果当前场在缓存不存在则保存
                    if (Boolean.FALSE.equals(hassed)) {
                        List<String> skuIds = po.getSkuItems().stream().map(item -> po.getId() + "_" + item.getSkuId()).toList();
                        stringRedisTemplate.opsForList().leftPushAll(key, skuIds);

                        Map<Long, SkuInfoTo> map = productFeignService.getInfoByIds(po.getSkuItems().stream().map(SeckillSkuRelationTo::getSkuId).toList());
                        //存入秒杀商品基本信息和详细信息
                        po.getSkuItems().forEach(item -> {
                            SkuInfoTo skuInfoTo = map.get(item.getSkuId());
                            item.setSkuInfoTo(skuInfoTo);
                            //秒杀时间
                            item.setStartTime(po.getStartTime().getTime());
                            item.setEndTime(po.getEndTime().getTime());
                            //为了保证公平秒杀加入商品随机码
                            String nano = IdUtil.nanoId();
                            item.setRandomValues(nano);
                            //秒杀信息key 场次id加skuId
                            hashOps.put(po.getId() + "_" + item.getSkuId(), JSON.toJSONString(item));

                            //为了保证高并发不超卖 加入信号量锁
                            RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_SEMAPHORE + po.getId() + "_" + item.getSkuId() + "_" + nano);
                            semaphore.trySetPermits(item.getSeckillCount().intValue());
                            log.info("已上架的场次id：{}，商品id：{}，商品秒杀数量：{}", po.getId(), item.getSkuId(), item.getSeckillCount());
                        });
                    }
                });
            }
        }
    }

    /**
     * 首页查询所有当天可以秒杀的秒杀商品信息
     *
     * @return
     */
    @Override
    public List<SeckillSkuRelationTo> getToDayCeckill() {
        //获取所有秒杀活动
        Set<String> keys = stringRedisTemplate.keys(SECKILL_SESSION + "*");
        if (CollUtil.isNotEmpty(keys)) {
            //获取当前时间
            long today = DateUtil.parseDate(DateUtil.formatDate(DateUtil.date())).getTime();
            long todayTime = DateUtil.parseDateTime(DateUtil.formatDateTime(DateUtil.date())).getTime();
            BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKUS);
            for (String key : keys) {
                String timePeriod = key.replaceAll(SECKILL_SESSION, "");
                String[] split = timePeriod.split("_");

                long stateDate = DateUtil.parseDate(DateUtil.formatDate(DateUtil.date(Long.parseLong(split[0])))).getTime();
                long endDate = DateUtil.parseDate(DateUtil.formatDate(DateUtil.date(Long.parseLong(split[1])))).getTime();
                if (today == stateDate && today <= endDate) {
                    //说明此秒杀活动包含并没有结束
                    List<String> values = stringRedisTemplate.opsForList().range(key, 0, -1);
                    if (MyCollUtil.isNotEmpty(values)) {

                        return values.stream().map(po -> {
                            String json = hashOps.get(po);
                            SeckillSkuRelationTo parsedObject = JSON.parseObject(json, SeckillSkuRelationTo.class);
                            if (todayTime >= parsedObject.getStartTime() && todayTime <= parsedObject.getEndTime()) {
                                //秒杀正在开始
                            } else {
                                //秒杀还未开始
                                parsedObject.setRandomValues(null);
                            }
                            return parsedObject;
                        }).toList();
                    }
                }
            }
        }
        return null;
    }

    @Override
    @SentinelResource(value = "skuCeckillResource",blockHandler = "skuCeckillResourceBlockHandler")
    public List<SeckillSkuRelationTo> getSkuCeckill(Long skuId) {
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKUS);
        Set<String> keys = hashOps.keys();

        try (Entry entry = SphU.asyncEntry("SkuCeckill")) {
            if (CollUtil.isNotEmpty(keys)) {
                List<SeckillSkuRelationTo> seckillSkuRelationTos = new ArrayList<>();
                long todayTime = DateUtil.parseDateTime(DateUtil.formatDateTime(DateUtil.date())).getTime();
                for (String key : keys) {
                    //任意场次拼上id
                    String regx = "\\d_" + skuId;
                    boolean matches = Pattern.matches(regx, key);
                    if (matches) {
                        //查询秒杀信息
                        String json = hashOps.get(key);
                        SeckillSkuRelationTo parsedObject = JSON.parseObject(json, SeckillSkuRelationTo.class);
                        if (Objects.nonNull(parsedObject)) {
                            if (todayTime >= parsedObject.getStartTime() && todayTime <= parsedObject.getEndTime()) {

                            } else {
                                parsedObject.setRandomValues(null);
                            }
                            seckillSkuRelationTos.add(parsedObject);
                        }
                    }
                }
                return seckillSkuRelationTos;
            }
        } catch (BlockException ex) {
            log.error("SkuCeckill流量被限制访问。。。。。：{}", ex.getMessage());
        }


        return null;
    }

    private List<SeckillSkuRelationTo> skuCeckillResourceBlockHandler(Long skuId, BlockException ex) {
        log.error("skuCeckillResourceBlockHandler流量被限制访问。。。。。：{}", ex.getMessage());
        return null;
    }

    /**
     * @param skuId
     * @param killId
     * @param randomValue
     * @param num
     * @return java.lang.String
     * @description 秒杀方法
     * @author zhangyiheng
     * @date 2024/7/5 13:07
     */
    @Override
    public String seckill(Long skuId, Long killId, String randomValue, Integer num) {
        //判断当前是否登录状态
        MemberTo memberTo = SeckillInterceptor.MEMBER.get();
        if (Objects.nonNull(memberTo)) {
            //查询指定场次的商品是否存在缓存数据
            BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKUS);
            String json = hashOps.get(killId + "_" + skuId);
            if (StrUtil.isNotBlank(json)) {
                //判断秒杀时间是否未开始或过期
                SeckillSkuRelationTo parsed = JSON.parseObject(json, SeckillSkuRelationTo.class);
                long toTime = new Date().getTime();
                if (toTime >= parsed.getStartTime() && toTime <= parsed.getEndTime()) {
                    //判断随机码是否正确
                    if (randomValue.equals(parsed.getRandomValues())) {
                        //判断当前购买数量是否超出限制
                        if (Objects.equals(num, parsed.getSeckillLimit().intValue())) {
                            //判断当前用户是否已经秒杀过商品
                            long ttl = parsed.getEndTime() - toTime;
                            Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(SECKILL_KILL_USER + killId + "_" + skuId + "_" + memberTo.getId(), killId + "_" + skuId, ttl, TimeUnit.MILLISECONDS);
                            if (Boolean.TRUE.equals(res)) {
                                //判断当前信号量是否能扣减
                                RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_SEMAPHORE + killId + "_" + skuId + "_" + randomValue);
                                boolean semaphoreRes = semaphore.tryAcquire(num);
                                if (semaphoreRes) {
                                    //存入mq 发送到订单服务，创建订单
                                    SeckillCreateTo seckillCreateTo = new SeckillCreateTo();
                                    seckillCreateTo.setNum(num);
                                    seckillCreateTo.setSkuId(skuId);
                                    seckillCreateTo.setSeckillPrice(parsed.getSeckillPrice());
                                    String uuid = IdUtil.fastSimpleUUID();
                                    seckillCreateTo.setOrderSn(uuid);
                                    seckillCreateTo.setMemberTo(memberTo);
                                    rabbitTemplate.convertAndSend(ORDER_EVENT_EXCHANGE, SECKILL_CREATE_ORDER, seckillCreateTo);
                                    return uuid;
                                }
                            }
                        }
                    }
                }
            }
        }


        return null;
    }
}
